package com.fima.cardsui;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;

/*import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.Animator.AnimatorListener;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.animation.ValueAnimator;
import com.nineoldandroids.view.ViewHelper;

import static com.nineoldandroids.view.ViewHelper.setAlpha;
import static com.nineoldandroids.view.ViewPropertyAnimator.animate;
*/

/**
 * A {@link android.view.View.OnTouchListener} that makes any {@link View}
 * dismissable when the user swipes (drags her finger) horizontally across the
 * view.
 * 
 * <p>
 * <em>For {@link android.widget.ListView} list items that don't manage their own touch events
 * (i.e. you're using
 * {@link android.widget.ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)}
 * or an equivalent listener on {@link android.app.ListActivity} or
 * {@link android.app.ListFragment}, use {@link SwipeDismissListViewTouchListener} instead.</em>
 * </p>
 * 
 * <p>
 * Example usage:
 * </p>
 * 
 * <pre>
 * view.setOnTouchListener(new SwipeDismissTouchListener(view, null, // Optional
 *                                                                     // token/cookie
 *                                                                     // object
 *         new SwipeDismissTouchListener.OnDismissCallback() {
 *             public void onDismiss(View view, Object token) {
 *                 parent.removeView(view);
 *             }
 *         }));
 * </pre>
 * 
 * <p>
 * This class Requires API level 12 or later due to use of
 * {@link android.view.ViewPropertyAnimator}.
 * </p>
 * 
 * @see SwipeDismissListViewTouchListener
 */
public class SwipeDismissTouchListener implements View.OnTouchListener {
    // Cached ViewConfiguration and system-wide constant values
    private int mSlop;
    private int mMinFlingVelocity;
    private int mMaxFlingVelocity;
    private long mAnimationTime;

    // Fixed properties
    private View mView;
    private OnDismissCallback mCallback;
    private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero

    // Transient properties
    private float mDownX;
    private boolean mSwiping;
    private Object mToken;
    private VelocityTracker mVelocityTracker;
    private float mTranslationX;

    /**
     * The callback interface used by {@link SwipeDismissTouchListener} to
     * inform its client about a successful dismissal of the view for which it
     * was created.
     */
    public interface OnDismissCallback {
        /**
         * Called when the user has indicated they she would like to dismiss the
         * view.
         * 
         * @param view
         *            The originating {@link View} to be dismissed.
         * @param token
         *            The optional token passed to this object's constructor.
         */
        void onDismiss(View view, Object token);
    }

    /**
     * Constructs a new swipe-to-dismiss touch listener for the given view.
     *
     * @param view
     *            The view to make dismissable.
     * @param token
     *            An optional token/cookie object to be passed through to the
     *            callback.
     * @param callback
     *            The callback to trigger when the user has indicated that she
     *            would like to dismiss this view.
     */
    public SwipeDismissTouchListener(View view, Object token, OnDismissCallback callback) {
        ViewConfiguration vc = ViewConfiguration.get(view.getContext());
        mSlop = vc.getScaledTouchSlop() * 2;
        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
        mAnimationTime = view.getContext().getResources().getInteger(android.R.integer.config_shortAnimTime);
        mView = view;
        mToken = token;
        mCallback = callback;
    }

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        // offset because the view is translated during swipe
        motionEvent.offsetLocation(mTranslationX, 0);

        if (mViewWidth < 2) {
            mViewWidth = mView.getWidth();
        }

        switch (motionEvent.getActionMasked()) {
        case MotionEvent.ACTION_DOWN: {
            // TODO: ensure this is a finger, and set a flag
            mDownX = motionEvent.getRawX();
            mVelocityTracker = VelocityTracker.obtain();
            mVelocityTracker.addMovement(motionEvent);
            view.onTouchEvent(motionEvent);
            return false;
        }

        case MotionEvent.ACTION_UP: {
            if (mVelocityTracker == null) {
                break;
            }

            float deltaX = motionEvent.getRawX() - mDownX;
            mVelocityTracker.addMovement(motionEvent);
            mVelocityTracker.computeCurrentVelocity(1000);
            float rawVelocityX = mVelocityTracker.getXVelocity();
            float velocityX = Math.abs(rawVelocityX);
            float velocityY = Math.abs(mVelocityTracker.getYVelocity());
            boolean dismiss = false;
            boolean dismissRight = false;

            if (((rawVelocityX > 0) == (deltaX > 0)) &&
                Math.abs(deltaX) >= Utils.convertDpToPixel(mView.getContext(), 20)) {
                if (Math.abs(deltaX) > mViewWidth / 2) {
                    dismiss = true;
                    dismissRight = deltaX > 0;
                } else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity && velocityY < velocityX) {
                    dismiss = true;
                    dismissRight = rawVelocityX > 0;
                }
            }
            if (dismiss) {
                // dismiss
                mView.animate().translationX(dismissRight ? mViewWidth : -mViewWidth).alpha(0).setDuration(mAnimationTime).setListener(new Animator.AnimatorListener() {

                    @Override
                    public void onAnimationStart(Animator arg0) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void onAnimationRepeat(Animator arg0) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void onAnimationEnd(Animator arg0) {
                        performDismiss();

                    }

                    @Override
                    public void onAnimationCancel(Animator arg0) {
                        // TODO Auto-generated method stub

                    }
                });
            } else {
                // cancel
                mView.animate().translationX(0).alpha(1).setDuration(mAnimationTime).setListener(null);
            }
            mVelocityTracker = null;
            mTranslationX = 0;
            mDownX = 0;
            mSwiping = false;
            break;
        }

        case MotionEvent.ACTION_MOVE: {
            if (mVelocityTracker == null) {
                break;
            }

            mVelocityTracker.addMovement(motionEvent);
            float deltaX = motionEvent.getRawX() - mDownX;
            if (Math.abs(deltaX) > mSlop) {
                mSwiping = true;
                mView.getParent().requestDisallowInterceptTouchEvent(true);

                // Cancel listview's touch
                MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
                cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (motionEvent.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
                mView.onTouchEvent(cancelEvent);
            }

            if (mSwiping) {
                mTranslationX = deltaX;
                mView.setTranslationX(deltaX);

                // TODO: use an ease-out interpolator or such
                mView.setAlpha(Math.max(0f, Math.min(1f, 1f - 2f * Math.abs(deltaX) / mViewWidth)));
                return true;
            }
            break;
        }
        }
        return false;
    }

    private void performDismiss() {
        // Animate the dismissed view to zero-height and then fire the dismiss
        // callback.
        // This triggers layout on each animation frame; in the future we may
        // want to do something
        // smarter and more performant.

        final ViewGroup.LayoutParams lp = mView.getLayoutParams();
        final int originalHeight = mView.getHeight();

        ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);

        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mCallback.onDismiss(mView, mToken);
                // Reset view presentation
                mView.setAlpha(1f);
                mView.setTranslationX(0);
                // mView.setAlpha(1f);
                // mView.setTranslationX(0);
                lp.height = originalHeight;
                mView.setLayoutParams(lp);
            }
        });

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                lp.height = (Integer) valueAnimator.getAnimatedValue();
                mView.setLayoutParams(lp);
            }
        });

        animator.start();
    }
}
